-
Notifications
You must be signed in to change notification settings - Fork 274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
allow to append comment to query #709
allow to append comment to query #709
Conversation
Unfortunately this is not going to work based on how most users use Ecto+Postgres because the queries are prepared: which means the query is only sent once to the database, the first time around. The reason why it works with BEGIN is because it is a textual command that is small and it is sent every time. |
Thanks, I did not know that. What about adding a mode that allows to send the whole query when there is a comment? Libraries in other languages send the whole sql afaik - python example, there is more in other languages in this repo. |
We can do that and say that passing a comment automatically disables query caching, but then I beleive the implementation here has to be different. I would generally try to deal with it at the protocol level though, instead of changing the binary payloads. |
I'm happy to work on that, I just need to better understand how postgrex works. |
You probably want to extract out the comment only in this branch: https://github.com/elixir-ecto/postgrex/pull/709/files#diff-0da854f0c1cda9486d776c72ecda6a2e595a7667b72688669bbd80d6b80f0f96R354 and pass the comment as argument to And, on Postgrex module, if there is a comment, you set postgrex_prepare to false. |
5aee3aa
to
be74cce
Compare
I'm considering whether it should be named differently, like |
end | ||
end | ||
|
||
@tag :big_binary |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes problems when I log all queries in postgres - this inserts all the data in the log file, I need to have a way to skip it.
Sorry if i'm way off the mark, I don't know if I completely understand the ask. But would it work to add |
When we have options to skip query caching would it be enough to add the comment in the query function where we call iodata_to_binary? instead of modifying the protocol file ? |
I have dropped some nitpicks. It generally looks good to me :) |
1878393
to
f20eec9
Compare
f20eec9
to
78a8069
Compare
After thinking abount it I wonder if this is exactly what I need. |
Given queries are composable, which module and function would you use? If you want to use the module and function from the repository, then it is somewhat dynamic, but it is already information that we compute anyway, so you are right we could tackle it at the builder. |
lib/postgrex/protocol.ex
Outdated
@@ -1593,6 +1606,16 @@ defmodule Postgrex.Protocol do | |||
transaction_error(s, postgres) | |||
end | |||
|
|||
defp parse_describe_comment_msgs(query, comment, tail) when is_binary(comment) do | |||
statement = query.statement <> "/*#{comment}*/" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems we may no longer go wit hthis PR, but I wonder if this could be dealt as IO lists:
statement = query.statement <> "/*#{comment}*/" | |
statement = [query.statement, "/*", comment, "*/"] |
So how do we proceed with that pr? |
lib/postgrex/protocol.ex
Outdated
false -> | ||
comment = Keyword.get(opts, :comment) | ||
|
||
if is_binary(comment) && String.contains?(comment, "*/") do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if is_binary(comment) && String.contains?(comment, "*/") do | |
if is_binary(comment) and String.contains?(comment, "*/") do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this validation to the client. There is no need to crash the connection on bad argument. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So should I just remove the whole if
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should move it to the client, to the Postgrex
module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it there in the query function.
In theory the comment could be also an iolist but Currently we are getting CaseClauseError.
I think this is hard to validate for sql injection unless we first dump it to a string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added one tiny nitpick and we can ship it!
💚 💙 💜 💛 ❤️ |
Thank you for leading this. I am sure people will appreciate this feature. Please give main a try in conjuction with Ecto and let us know if it works as expected. |
Thanks, I will try it over the weekend. |
We missed something when skipping prepare - I need to dig deeper into it, but that's what I currently have: defmodule W do
require Test.Repo
alias Test.Repo
import Ecto.Query
def insert do
weather1 = %Weather{temp_lo: 0, temp_hi: 23}
Repo.insert!(weather1)
end
def get_query do
values = [%{id: 1, name: "Zabrze"}, %{id: 2, name: "Dudley"}]
Weather
|> join(:inner, [w], w1 in values(values, %{id: :integer, name: :string}), on: w.id == w1.id)
|> select([w, c], %Weather{id: w.id, city: c.name})
|> Repo.all()
end
def get_schema do
Weather
|> select([w], %Weather{id: w.id})
|> where([w], not is_nil(w.id))
|> Repo.all()
end
end And calling it one after one, I'm getting the comment only on one of the queries:
as you see only one is unnamed. |
@dkuku in Ecto, we pass a cache_statement to Postgrex.query, and I think that's overriding the We should probably ignore the cache statement if a comment is given. Also, you shouldn't need to replace all Ecto.Repo functions. We already support |
This is getting a bit complicated, I have to pass the comment around too many function and still it is not shown everywhere. I have also a question about the stacktrace - do you have any suggestion: {:all,
#Ecto.Query<from w0 in Weather, where: not is_nil(w0.id),
select: %Weather{id: w0.id}>,
[
stacktrace: [
{Ecto.Repo.Supervisor, :tuplet, 2, [file: ~c"lib/ecto/repo/supervisor.ex", line: 179]},
{Test.Repo, :all, 2, [file: ~c"livemd/ecto.livemd#cell:pwz2dlcaejzj2duk", line: 2]},
{:elixir, :eval_external_handler, 3, [file: ~c"src/elixir.erl", line: 386]},
{:erl_eval, :do_apply, 7, [file: ~c"erl_eval.erl", line: 904]},
{:elixir, :eval_forms, 4, [file: ~c"src/elixir.erl", line: 364]},
{Module.ParallelChecker, :verify, 1, [file: ~c"lib/module/parallel_checker.ex", line: 112]},
{Livebook.Runtime.Evaluator, :"-eval/4-fun-0-", 3, [file: ~c"lib/livebook/runtime/evaluator.ex", line: 635]},
{Code, :with_diagnostics, 2, [file: ~c"lib/code.ex", line: 621]},
{Livebook.Runtime.Evaluator, :eval, 4, [file: ~c"lib/livebook/runtime/evaluator.ex", line: 629]},
{Livebook.Runtime.Evaluator, :continue_do_evaluate_code, 6, [file: ~c"lib/livebook/runtime/evaluator.ex", line: 437]},
{Livebook.Runtime.Evaluator, :loop, 1, [file: ~c"lib/livebook/runtime/evaluator.ex", line: 337]},
{:proc_lib, :init_p_do_apply, 3, [file: ~c"proc_lib.erl", line: 329]}
]
]} I gave up and went back to the module I made. Then the stacktrace looks like i expect - the module is on the second line. I tested it like 20 times switching between my implementation and Ecto. {:all,
#Ecto.Query<from w0 in Weather, where: not is_nil(w0.id),
select: %Weather{id: w0.id}>,
[
stacktrace: [
{Ecto.Repo.Supervisor, :tuplet, 2, [file: ~c"lib/ecto/repo/supervisor.ex", line: 179]},
{WeatherContext, :get_schema, 0, [file: ~c"livemd/ecto.livemd#cell:4h52ky3654zcawdu", line: 32]},
{:elixir, :eval_external_handler, 3, [file: ~c"src/elixir.erl", line: 386]},
{:erl_eval, :do_apply, 7, [file: ~c"erl_eval.erl", line: 904]},
{:elixir, :eval_forms, 4, [file: ~c"src/elixir.erl", line: 364]},
{Module.ParallelChecker, :verify, 1, [file: ~c"lib/module/parallel_checker.ex", line: 112]},
{Livebook.Runtime.Evaluator, :"-eval/4-fun-0-", 3, [file: ~c"lib/livebook/runtime/evaluator.ex", line: 635]},
{Code, :with_diagnostics, 2, [file: ~c"lib/code.ex", line: 621]},
{Livebook.Runtime.Evaluator, :eval, 4, [file: ~c"lib/livebook/runtime/evaluator.ex", line: 629]},
{Livebook.Runtime.Evaluator, :continue_do_evaluate_code, 6, [file: ~c"lib/livebook/runtime/evaluator.ex", line: 437]},
{Livebook.Runtime.Evaluator, :loop, 1, [file: ~c"lib/livebook/runtime/evaluator.ex", line: 337]},
{:proc_lib, :init_p_do_apply, 3, [file: ~c"proc_lib.erl", line: 329]}
] |
I mentioned this in my previous comment:
It doesn't work? That should alleviate the need to pass comments. You should only need to do it on prepare_query/prepare_options/etc.
Yes, don't use the shell/livebook. Those are evaluated. Stacktrace is precise on compiled code. |
Recently there was introduced commit_comment to prepend comment before the commit keyword
I wanted to propose something similar for all queries - It will be useful for prepending sqlcommenter comments to queries but any other info is possible.
I tried different hacks to get around this limitation but this has to be implemented in postgrex just before the query is sent to the database.
I found the place where to add this code by trial and error, any suggestion welcome how to make it better.
Also is it possible to test it somehow? Currently I'm logging it in the database but it would be nice to log the data that is sent to the database. Maybe testing the encoder is enough?